const JmxExecutor = require("./JmxExecutor");
const JmxRuntime = require("./JmxRuntime");

class JmxScheduler {

    /**
     * 
     * @param {string} jmeterHomePath 
     * @param {{testPlanName: string, jmxFilePath: string, initialThreadNumber: number, threadNumberIncrement: number}[]} testPlans
     * @param {number} duration 
     * @param {string} jtlPath 
     * @param {number} maxRound 
     */
    constructor(jmeterHomePath, testPlans, duration, jtlPath, maxRound = 100) {
        /** @type {JmxExecutor} */
        this.jmxExecutor = new JmxExecutor(jmeterHomePath);
        /** @type {JmxRuntime[]} */
        this.jmxRuntimes = testPlans.map(jmx => new JmxRuntime(this.jmxExecutor, jmx.jmxFilePath, jmx.initialThreadNumber, duration, jtlPath));
        this.threadNumberIncrement = testPlans.reduce((acc, jmx) => {
            acc[jmx.testPlanName] = jmx.threadNumberIncrement;
            return acc;
        }, {});
        this.maxRound = maxRound;
        this.testResults = [];
    }

    async runOnce() {
        const promises = [];
        for (const jmxRuntime of this.jmxRuntimes) {
            const promise = jmxRuntime.run();
            promises.push(promise);
        }
        await Promise.all(promises);
        this.testResults.push(this.jmxRuntimes.map(jmxRuntime => {
            return {
                testPlanName: jmxRuntime.testPlanName,
                ...jmxRuntime.testResults[jmxRuntime.testResults.length - 1]
            }
        }));
        await (function () {
            return new Promise(resolve => setTimeout(resolve, 30000))
        })();
    }

    async schedule() {
        // 基准测试
        await this.runOnce();
        if (this.jmxRuntimes.some(jmxRuntime => this.isBottleneck(jmxRuntime))) {
            return;
        }
        this.jmxRuntimes.forEach(jmxRuntime => {
            jmxRuntime.threadNumber = jmxRuntime.threadNumber + this.threadNumberIncrement[jmxRuntime.testPlanName];
        });

        // 负载测试
        let isAllBottleneck = false;
        let round = 0;
        while (round < this.maxRound && !isAllBottleneck) {
            await this.runOnce();
            this.jmxRuntimes.forEach(jmxRuntime => {
                if (!jmxRuntime.isBottleneck) {
                    if (this.isBottleneck(jmxRuntime)) {
                        jmxRuntime.isBottleneck = true;
                        jmxRuntime.threadNumber = jmxRuntime.threadNumber - this.threadNumberIncrement[jmxRuntime.testPlanName];
                    }
                    else {
                        jmxRuntime.threadNumber = jmxRuntime.threadNumber + this.threadNumberIncrement[jmxRuntime.testPlanName];
                    }
                }
            });
            isAllBottleneck = this.jmxRuntimes.every(jmxRuntime => jmxRuntime.isBottleneck);
            round++;
        }
    }

    /**
     * 
     * @param {JmxRuntime} jmxRuntime 
     * @returns {boolean}
     */
    isBottleneck(jmxRuntime) {
        if (jmxRuntime.round === 0) {
            return false;
        }
        if (jmxRuntime.round === 1) {
            if (jmxRuntime.testResults[jmxRuntime.round - 1].errorRate > 1) {
                return true;
            }
            return false;
        }
        const currentTestResult = jmxRuntime.testResults[jmxRuntime.round - 1];
        const previousTestResult = jmxRuntime.testResults[jmxRuntime.round - 2];
        const throughputChange = Math.abs((currentTestResult.actualThroughput - previousTestResult.actualThroughput) / previousTestResult.actualThroughput);
        const responseTimeChange = (currentTestResult.averageResponseTime - previousTestResult.averageResponseTime) / previousTestResult.averageResponseTime;
        if ((throughputChange < 0.2 && responseTimeChange > 0.2) || (currentTestResult.errorRate > 1)) {
            return true;
        }
        return false;
    }

}

module.exports = JmxScheduler;